/*- * Copyright (C) 2011-2014 by Iwao AVE! * This program is made available under the terms of the MIT License. */ package org.eclipselabs.stlipse.cache; import java.beans.Introspector; import java.util.List; import java.util.Map; import org.eclipse.core.runtime.Status; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.AnonymousClassDeclaration; import org.eclipse.jdt.core.dom.FieldDeclaration; import org.eclipse.jdt.core.dom.IAnnotationBinding; import org.eclipse.jdt.core.dom.IMemberValuePairBinding; import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.Modifier; import org.eclipse.jdt.core.dom.PrimitiveType; import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.jdt.core.dom.Type; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.core.dom.VariableDeclarationFragment; import org.eclipselabs.stlipse.Activator; /** * @author Iwao AVE! */ public class BeanPropertyVisitor extends ASTVisitor { private IJavaProject project; private final String qualifiedName; private final Map<String, String> readableFields; private final Map<String, String> writableFields; private final Map<String, EventProperty> eventHandlers; private int nestLevel; public BeanPropertyVisitor( IJavaProject project, String qualifiedName, Map<String, String> readableFields, Map<String, String> writableFields, Map<String, EventProperty> eventHandlers) { super(); this.project = project; this.qualifiedName = qualifiedName; this.readableFields = readableFields; this.writableFields = writableFields; this.eventHandlers = eventHandlers; } @Override public boolean visit(TypeDeclaration node) { ITypeBinding binding = node.resolveBinding(); if (qualifiedName.equals(binding.getQualifiedName())) nestLevel = 1; else if (nestLevel > 0) nestLevel++; return true; } @Override public boolean visit(AnonymousClassDeclaration node) { return false; } @Override public boolean visit(FieldDeclaration node) { if (nestLevel != 1) return false; int modifiers = node.getModifiers(); if (Modifier.isPublic(modifiers)) { @SuppressWarnings("unchecked") List<VariableDeclarationFragment> fragments = node.fragments(); for (VariableDeclarationFragment fragment : fragments) { String fieldName = fragment.getName().toString(); String qualifiedName = getQualifiedNameFromType(node.getType()); if (qualifiedName == null) ; // ignore else { readableFields.put(fieldName, qualifiedName); if (!Modifier.isFinal(modifiers)) writableFields.put(fieldName, qualifiedName); } } } return false; } @Override public boolean visit(MethodDeclaration node) { if (nestLevel != 1) return false; // Resolve binding first to support Lombok generated methods. // node.getModifiers() returns incorrect access modifiers for them. // https://github.com/harawata/stlipse/issues/2 IMethodBinding method = node.resolveBinding(); if (Modifier.isPublic(method.getModifiers())) { final String methodName = node.getName().toString(); final int parameterCount = node.parameters().size(); final Type returnType = node.getReturnType2(); if (returnType == null) { // Ignore constructor } else if (isReturnVoid(returnType)) { if (isSetter(methodName, parameterCount)) { SingleVariableDeclaration param = (SingleVariableDeclaration)node.parameters().get(0); String qualifiedName = getQualifiedNameFromType(param.getType()); String fieldName = getFieldNameFromAccessor(methodName); writableFields.put(fieldName, qualifiedName); } } else { if (isGetter(methodName, parameterCount)) { String fieldName = getFieldNameFromAccessor(methodName); String qualifiedName = getQualifiedNameFromType(returnType); readableFields.put(fieldName, qualifiedName); } else if (isEventHandler(returnType.toString(), parameterCount)) { ITypeBinding binding = returnType.resolveBinding(); if (binding == null) { Activator.log(Status.INFO, "Couldn't resolve binding for return type " + returnType.toString() + " of method " + methodName); } else { String qualifiedName = binding.getQualifiedName(); try { if (TypeCache.isResolution(project, project.findType(qualifiedName))) { Object annotationValue = null; boolean isDefaultHandler = false; boolean isInterceptor = false; for (IAnnotationBinding annotation : method.getAnnotations()) { String name = annotation.getName(); isInterceptor |= ("Before".equals(name) || "After".equals(name)); if (isInterceptor) break; isDefaultHandler |= "DefaultHandler".equals(name); if ("HandlesEvent".equals(name)) { IMemberValuePairBinding[] valuePairs = annotation.getAllMemberValuePairs(); for (IMemberValuePairBinding valuePair : valuePairs) { if ("value".equals(valuePair.getName())) { annotationValue = valuePair.getValue(); } } } } if (!isInterceptor) { EventProperty eventProperty = new EventProperty(); eventProperty.setDefaultHandler(isDefaultHandler); eventProperty.setMethodName(methodName); String eventName = (String)(annotationValue == null ? methodName : annotationValue); eventHandlers.put(eventName, eventProperty); } } } catch (JavaModelException e) { Activator.log(Status.WARNING, "Error occurred during event handler check", e); } } } } } return false; } private String getQualifiedNameFromType(Type type) { String qualifiedName = null; ITypeBinding binding = type.resolveBinding(); if (binding != null) { if (binding.isParameterizedType()) { ITypeBinding[] arguments = binding.getTypeArguments(); // length = 1 -> List, length > 1 -> Map qualifiedName = arguments[arguments.length > 1 ? 1 : 0].getQualifiedName(); } else { qualifiedName = binding.getQualifiedName(); } } return qualifiedName; } private boolean isEventHandler(String returnType, int parameterCount) { // Just a quick check here. return returnType.endsWith("Resolution") && parameterCount == 0; } public static boolean isGetter(String methodName, int parameterCount) { return (methodName.startsWith("get") && methodName.length() > 3) || (methodName.startsWith("is") && methodName.length() > 2) && parameterCount == 0; } public static boolean isSetter(String methodName, int parameterCount) { return methodName.startsWith("set") && methodName.length() > 3 && parameterCount == 1; } private boolean isReturnVoid(Type type) { return type.isPrimitiveType() && PrimitiveType.VOID.equals(((PrimitiveType)type).getPrimitiveTypeCode()); } @Override public void endVisit(TypeDeclaration node) { if (nestLevel == 1) { Type superclassType = node.getSuperclassType(); if (superclassType != null) { ITypeBinding binding = superclassType.resolveBinding(); BeanPropertyCache.parseBean(project, binding.getQualifiedName(), readableFields, writableFields, eventHandlers); } } nestLevel--; } public static String getFieldNameFromAccessor(String methodName) { String fieldName = ""; if (methodName != null) { if (methodName.startsWith("set") || methodName.startsWith("get")) { fieldName = Introspector.decapitalize(methodName.substring(3)); } else if (methodName.startsWith("is")) { fieldName = Introspector.decapitalize(methodName.substring(2)); } } return fieldName; } }